#include <stdlib.h>

#include "../host/host.h"
#include "../core.h"
#include "../log.h"
#include "../memmap.h"
#include "gpu.h"

#include "display.h"

#define VRAM_START 0x04000000

#define xres 480
#define yres 272

u32 framebuf = 0;
#ifndef NO_THREAD
Mutex fbMtx;
#endif
void *realFb = NULL;
u32 fbSize = 0;
u32 lineSize = 0;

s8 colorSize = 0;
s8 sceneChanged = 0;

typedef struct
{
    u32 fbAddr;
    u32 lineSize;
    ScePixelFormat pixelSize;
} FbInfo;

FbInfo fbInfo;

void display_init()
{
#ifndef NO_THREAD
    cs_init(&fbMtx, "Framebuffer");
#endif
}

void display_free()
{
    free(realFb);

#ifndef NO_THREAD
    cs_free(&fbMtx);
#endif
}

void display_dispInit()
{
    host_dispInit();
    host_geInit();
}

void display_dispFree()
{
    host_geFree();
    host_dispFree();
}

s8 display_fbIsSet()
{
    if (host_fbIsSet())
    {
        s8 ret;
        cs_enter(&fbMtx);
        ret = mem_isOk(framebuf);
        cs_leave(&fbMtx);
        return ret;
    }
    return 0;
}

void display_setFbTh()
{
    cs_enter(&fbMtx);
    framebuf = fbInfo.fbAddr;

    lineSize = fbInfo.lineSize;

    switch (fbInfo.pixelSize)
    {
    case SCE_PIXELFORMAT_565:
    case SCE_PIXELFORMAT_5551:
    case SCE_PIXELFORMAT_4444:
        fbSize = sizeof(u16) * xres * yres;
        colorSize = 2;
        break;

    case SCE_PIXELFORMAT_8888:
        fbSize = sizeof(u32) * xres * yres;
        colorSize = 4;
        break;
    }

    if (realFb)
        realFb = realloc(realFb, fbSize);
    else
        realFb = malloc(fbSize);
    cs_leave(&fbMtx);

    host_setFb(lineSize, fbInfo.pixelSize, fbSize);
}

void display_setFb(u32 fbAddr, u32 lineSz, ScePixelFormat pixelSize)
{
#ifdef NO_THREAD
    display_setFbTh();
#else
    cs_enter(&fbMtx);
    fbInfo.fbAddr = fbAddr;
    fbInfo.lineSize = lineSz;
    fbInfo.pixelSize = pixelSize;
    cs_leave(&fbMtx);

    core_addToGfxThStack(GFXTH_SETFB, 0);
#endif
}

void display_syncFb()
{
    u32 x, y;

    if (!display_fbIsSet())
        return;

    cs_enter(&fbMtx);
    for (y = 0; y < yres; y++)
    {
        for (x = 0; x < xres; x++)
        {
            if (colorSize == 2)
                ((u16*)realFb)[(yres - y - 1) * xres + x] = mem_read16(framebuf + (y * lineSize + x) * 2);
            else if (colorSize == 4)
                ((u32*)realFb)[(yres - y - 1) * xres + x] = mem_read32(framebuf + (y * lineSize + x) * 4);
        }
    }
    cs_leave(&fbMtx);

    host_syncFb(realFb);
}

void display_syncScene()
{
    void *ptr;
    u32 x, y;
    u32 geFbAddr;

    if (!display_fbIsSet())
        return;

    if (!sceneChanged)
        return;
    sceneChanged = 0;

    geFbAddr = (gstate.frameBufferHigh << 24) | gstate.frameBufferLow;
    if (geFbAddr < VRAM_START)
        geFbAddr += VRAM_START;
    if (!mem_isOk(geFbAddr)) {
        _log(ERR, GFX, "Failed to write at %08x", geFbAddr);
        return;
    }

    ptr = host_initScenePtr();
    if (!ptr) {
        _log(ERR, GFX, "Invalid scene pointer?");
        return;
    }

    cs_enter(&fbMtx);
    for (y = 0; y < yres; y++)
    {
        for (x = 0; x < xres; x++)
        {
            if (colorSize == 2)
                mem_write16(geFbAddr + ((yres - y - 1) * gstate.frameBufferWidth + x) * 2, ((u16*)ptr)[y * xres + x]);
            else if (colorSize == 4)
                mem_write32(geFbAddr + ((yres - y - 1) * gstate.frameBufferWidth + x) * 4, ((u32*)ptr)[y * xres + x]);
        }
    }
    cs_leave(&fbMtx);

    host_freeScenePtr();
}

/* Finish the current 3d drawing to complete */
void display_finish()
{
    host_dispFinish();
}

void display_sceneChanged()
{
    sceneChanged = 1;
}

#ifndef NO_THREAD
void display_sync(u8 nextFrame)
{
    core_addToGfxThStack(GFXTH_FINISH, nextFrame);
}

void display_syncFromTh(u8 nextFrame)
#else
void display_sync(u8 nextFrame)
#endif
{
    if (nextFrame)
        host_nextFrame();
    display_finish();
    display_syncScene();
    display_syncFb();
}

